﻿namespace LastfmNet.Api
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using LastfmNet.Models;
    using System.Globalization;

    /// <summary>
    /// Last.fm API album methods
    /// </summary>
    public class AlbumApi
    {
        /// <summary>
        /// Reference to the Lastfm host class
        /// </summary>
        private readonly Lastfm lastfmApi;

        /// <summary>
        /// Initializes a new instance of the AlbumApi class.
        /// </summary>
        /// <param name="lastfmApi">Reference to the Lastfm host class</param>
        internal AlbumApi(Lastfm lastfmApi)
        {
            this.lastfmApi = lastfmApi;
        }

        /// <summary>
        /// Tag an album using a list of user supplied tags.
        /// This service requires authentication.
        /// </summary>
        /// <param name="artist">The artist name</param>
        /// <param name="album">The album name</param>
        /// <param name="tags">List of user supplied tags to apply to this album. Accepts a maximum of 10 tags.</param>
        public void AddTags(string artist, string album, IList<string> tags)
        {
            if (!this.lastfmApi.Session.Authenticated)
            {
                throw new LastfmNetException("This service requires authentication.");
            }

            LastfmParameters parameters = new LastfmParameters("album.addTags");
            parameters["artist"] = artist;
            parameters["album"] = album;
            parameters["tags"] = string.Join(",", tags);
            parameters.AddApiKey(this.lastfmApi.Session.ApiKey);
            parameters.AddSessionKey(this.lastfmApi.Session.SessionKey);
            parameters.Sign(this.lastfmApi.Session.ApiSecret);
            try
            {
                this.lastfmApi.MakeRequest(parameters, HttpMethod.Post);
            }
            catch (Exception ex)
            {
                throw new LastfmNetException("Unable to add tags for artist! (" + ex.Message + ")", ex);
            }
        }

        /// <summary>
        /// Get a list of Buy Links for a particular Album. It is required that you supply the artist and track params.
        /// This service does not require authentication. 
        /// </summary>
        /// <param name="artist">The artist name</param>
        /// <param name="album">The album</param>
        /// <param name="country">A country name, as defined by the ISO 3166-1 country names standard.</param>
        /// <param name="autocorrect">Transform misspelled artist names into correct artist names, returning the correct version instead. The corrected artist name will be returned in the response.</param>
        /// <returns>A list of Buy Links for a particular Album</returns>
        /// <remarks>Autocorrect is not working for this method in the Last.fm API</remarks>
        public Affiliations GetBuylinks(string artist, string album, string country, bool autocorrect = false)
        {
            LastfmParameters parameters = new LastfmParameters("album.getBuylinks");
            parameters["artist"] = artist;
            parameters["album"] = album;
            parameters["country"] = country;
            //parameters["autocorrect"] = autocorrect.ToInteger().ToString();
            parameters.AddApiKey(this.lastfmApi.Session.ApiKey);

            try
            {
                LastfmResponse result = this.lastfmApi.MakeRequest(parameters);
                return new Affiliations(result.MainNode);
            }
            catch (Exception ex)
            {
                throw new LastfmNetException("Unable to get buy links! (" + ex.Message + ")", ex);
            }
        }

        /// <summary>
        /// Get a list of Buy Links for a particular Album. It is required that you supply either the mbid param.
        /// This service does not require authentication. 
        /// </summary>
        /// <param name="mbid">The musicbrainz id for the album</param>
        /// <param name="country">A country name, as defined by the ISO 3166-1 country names standard.</param>
        /// <returns>A list of Buy Links for a particular Album</returns>
        public Affiliations GetBuylinks(string mbid, string country)
        {
            LastfmParameters parameters = new LastfmParameters("album.getBuylinks");
            parameters["mbid"] = mbid;
            parameters["country"] = country;
            parameters.AddApiKey(this.lastfmApi.Session.ApiKey);

            try
            {
                LastfmResponse result = this.lastfmApi.MakeRequest(parameters);
                return new Affiliations(result.MainNode);
            }
            catch (Exception ex)
            {
                throw new LastfmNetException("Unable to get buy links! (" + ex.Message + ")", ex);
            }
        }

        /// <summary>
        /// Get the metadata for an album on Last.fm using the album name. See playlist.fetch on how to get the album playlist. 
        /// This service does not require authentication. 
        /// </summary>
        /// <param name="artist">The artist name</param>
        /// <param name="album">The album name</param>
        /// <param name="lang">The language to return the biography in, expressed as an ISO 639 alpha-2 code.</param>
        /// <param name="autocorrect">Transform misspelled artist names into correct artist names, returning the correct version instead. The corrected artist name will be returned in the response.</param>
        /// <param name="username">The username for the context of the request. If supplied, the user's playcount for this album is included in the response.</param>
        /// <returns>The metadata for an album</returns>
        public Album GetInfo(string artist, string album, string lang = null, bool autocorrect = false, string username = null)
        {
            LastfmParameters parameters = new LastfmParameters("album.getInfo");
            parameters["artist"] = artist;
            parameters["album"] = album;
            if (lang != null)
            {
                parameters["lang"] = lang;
            }

            parameters["autocorrect"] = autocorrect.ToInteger().ToString(CultureInfo.InvariantCulture);
            if (username != null)
            {
                parameters["username"] = username;
            }

            parameters.AddApiKey(this.lastfmApi.Session.ApiKey);
            try
            {
                LastfmResponse result = this.lastfmApi.MakeRequest(parameters);
                return new Album(result.MainNode);
            }
            catch (Exception ex)
            {
                throw new LastfmNetException("Unable to get album info! (" + ex.Message + ")", ex);
            }
        }

        /// <summary>
        /// Get the metadata for an album on Last.fm using a musicbrainz id. See playlist.fetch on how to get the album playlist. 
        /// This service does not require authentication. 
        /// </summary>
        /// <param name="mbid">The musicbrainz id for the album</param>
        /// <param name="lang">The language to return the biography in, expressed as an ISO 639 alpha-2 code.</param>
        /// <param name="username">The username for the context of the request. If supplied, the user's playcount for this album is included in the response.</param>
        /// <returns>The metadata for an album </returns>
        public Album GetInfo(string mbid, string lang = null, string username = null)
        {
            LastfmParameters parameters = new LastfmParameters("album.getInfo");
            parameters["mbid"] = mbid;
            if (lang != null)
            {
                parameters["lang"] = lang;
            }

            if (username != null)
            {
                parameters["username"] = username;
            }

            parameters.AddApiKey(this.lastfmApi.Session.ApiKey);
            try
            {
                LastfmResponse result = this.lastfmApi.MakeRequest(parameters);
                return new Album(result.MainNode);
            }
            catch (Exception ex)
            {
                throw new LastfmNetException("Unable to get album info! (" + ex.Message + ")", ex);
            }
        }

        /// <summary>
        /// Get shouts for this album. 
        /// This service does not require authentication. 
        /// </summary>
        /// <param name="artist">The artist name</param>
        /// <param name="album">The album name</param>
        /// <param name="autocorrect">Transform misspelled artist names into correct artist names, returning the correct version instead. The corrected artist name will be returned in the response.</param>
        /// <param name="page">The page number to fetch. Defaults to first page.</param>
        /// <param name="limit">The number of results to fetch per page. Defaults to 50.</param>
        /// <returns>Shouts for this album</returns>
        public LastfmPager<Shout> GetShouts(ref string artist, ref string album, bool autocorrect = false, int page = 1, int limit = 50)
        {
            LastfmParameters parameters = new LastfmParameters("album.getShouts");
            parameters["artist"] = artist;
            parameters["album"] = album;
            parameters["autocorrect"] = autocorrect.ToInteger().ToString(CultureInfo.InvariantCulture);
            parameters["page"] = page.ToString(CultureInfo.InvariantCulture);
            parameters["limit"] = limit.ToString(CultureInfo.InvariantCulture);
            parameters.AddApiKey(this.lastfmApi.Session.ApiKey);
            try
            {
                LastfmResponse result = this.lastfmApi.MakeRequest(parameters);
                if (autocorrect)
                {
                    artist = result.MainNode.GetAttributeValue("artist");
                    album = result.MainNode.GetAttributeValue("album");
                }
                return new LastfmNet.Models.LastfmPager<Shout>(result.MainNode);
            }
            catch (Exception ex)
            {
                throw new LastfmNetException("Unable to get album shouts! (" + ex.Message + ")", ex);
            }
        }

        /// <summary>
        /// Get shouts for this album. 
        /// This service does not require authentication. 
        /// </summary>
        /// <param name="mbid">The musicbrainz id for the album</param>
        /// <param name="page">The page number to fetch. Defaults to first page.</param>
        /// <param name="limit">The number of results to fetch per page. Defaults to 50.</param>
        /// <returns>Shouts for this album</returns>
        public LastfmPager<Shout> GetShouts(string mbid, int page = 1, int limit = 50)
        {
            LastfmParameters parameters = new LastfmParameters("album.getShouts");
            parameters["mbid"] = mbid;
            parameters["page"] = page.ToString(CultureInfo.InvariantCulture);
            parameters["limit"] = limit.ToString(CultureInfo.InvariantCulture);
            parameters.AddApiKey(this.lastfmApi.Session.ApiKey);
            try
            {
                LastfmResponse result = this.lastfmApi.MakeRequest(parameters);
                return new LastfmNet.Models.LastfmPager<Shout>(result.MainNode);
            }
            catch (Exception ex)
            {
                throw new LastfmNetException("Unable to get album shouts! (" + ex.Message + ")", ex);
            }
        }

        /// <summary>
        /// Get the tags applied by an individual user to an album on Last.fm. 
        /// This service requires authentication. 
        /// </summary>
        /// <param name="artist">The artist name</param>
        /// <param name="album">The album name</param>
        /// <param name="autocorrect">Transform misspelled artist names into correct artist names, returning the correct version instead. The corrected artist name will be returned in the response.</param>
        /// <returns>The tags applied by an individual user to an album on Last.fm</returns>
        public LastfmPager<TopTag> GetTags(ref string artist, ref string album, bool autocorrect = false)
        {
            // TODO: Test
            if (!this.lastfmApi.Session.Authenticated)
            {
                throw new LastfmNetException("This service requires authentication.");
            }

            LastfmParameters parameters = new LastfmParameters("album.getTags");
            parameters["artist"] = artist;
            parameters["album"] = album;
            parameters["autocorrect"] = autocorrect.ToInteger().ToString(CultureInfo.InvariantCulture);
            parameters.AddApiKey(this.lastfmApi.Session.ApiKey);
            parameters.AddSessionKey(this.lastfmApi.Session.SessionKey);
            parameters.Sign(this.lastfmApi.Session.ApiSecret);
            try
            {
                LastfmResponse result = this.lastfmApi.MakeRequest(parameters);
                if (autocorrect)
                {
                    artist = result.MainNode.GetAttributeValue("artist");
                    album = result.MainNode.GetAttributeValue("album");
                }
                return new LastfmNet.Models.LastfmPager<TopTag>(result.MainNode, "tag");
            }
            catch (Exception ex)
            {
                throw new LastfmNetException("Unable to get album tags! (" + ex.Message + ")", ex);
            }
        }

        /// <summary>
        /// Get the tags applied by an individual user to an album on Last.fm. 
        /// This service requires authentication. 
        /// </summary>
        /// <param name="mbid">The musicbrainz id for the album</param>
        /// <returns>The tags applied by an individual user to an album on Last.fm</returns>
        public LastfmPager<TopTag> GetTags(string mbid)
        {
            // TODO: Test
            if (!this.lastfmApi.Session.Authenticated)
            {
                throw new LastfmNetException("This service requires authentication.");
            }

            LastfmParameters parameters = new LastfmParameters("album.getTags");
            parameters["mbid"] = mbid;
            parameters.AddApiKey(this.lastfmApi.Session.ApiKey);
            parameters.AddSessionKey(this.lastfmApi.Session.SessionKey);
            parameters.Sign(this.lastfmApi.Session.ApiSecret);
            try
            {
                LastfmResponse result = this.lastfmApi.MakeRequest(parameters);
                return new LastfmPager<TopTag>(result.MainNode, "tag");
            }
            catch (Exception ex)
            {
                throw new LastfmNetException("Unable to get album tags! (" + ex.Message + ")", ex);
            }
        }

        /// <summary>
        /// Get the top tags for an album on Last.fm, ordered by popularity. 
        /// This service does not require authentication. 
        /// </summary>
        /// <param name="artist">The artist name</param>
        /// <param name="album">The album name</param>
        /// <param name="autocorrect">Transform misspelled artist names into correct artist names, returning the correct version instead. The corrected artist name will be returned in the response.</param>
        /// <returns>The top tags for an album on Last.fm, ordered by popularity</returns>
        public LastfmPager<TopTag> GetTopTags(ref string artist, ref string album, bool autocorrect = false)
        {
            LastfmParameters parameters = new LastfmParameters("album.getTopTags");
            parameters["artist"] = artist;
            parameters["album"] = album;
            parameters["autocorrect"] = autocorrect.ToInteger().ToString(CultureInfo.InvariantCulture);
            parameters.AddApiKey(this.lastfmApi.Session.ApiKey);
            try
            {
                LastfmResponse result = this.lastfmApi.MakeRequest(parameters);
                if (autocorrect)
                {
                    artist = result.MainNode.GetAttributeValue("artist");
                    album = result.MainNode.GetAttributeValue("album");
                }
                return new LastfmNet.Models.LastfmPager<TopTag>(result.MainNode, "tag");
            }
            catch (Exception ex)
            {
                throw new LastfmNetException("Unable to get album top tags! (" + ex.Message + ")", ex);
            }
        }

        /// <summary>
        /// Get the top tags for an album on Last.fm, ordered by popularity. 
        /// This service does not require authentication. 
        /// </summary>
        /// <param name="mbid">The musicbrainz id for the album</param>
        /// <returns>The top tags for an album on Last.fm, ordered by popularity</returns>
        public LastfmPager<TopTag> GetTopTags(string mbid)
        {
            LastfmParameters parameters = new LastfmParameters("album.getTopTags");
            parameters["mbid"] = mbid;
            parameters.AddApiKey(this.lastfmApi.Session.ApiKey);
            try
            {
                LastfmResponse result = this.lastfmApi.MakeRequest(parameters);
                return new LastfmPager<TopTag>(result.MainNode, "tag");
            }
            catch (Exception ex)
            {
                throw new LastfmNetException("Unable to get album top tags! (" + ex.Message + ")", ex);
            }
        }

        /// <summary>
        /// Remove a user's tag from an album. 
        /// This service requires authentication. 
        /// </summary>
        /// <param name="artist">The artist name</param>
        /// <param name="album">The album name</param>
        /// <param name="tag">A single user tag to remove from this album.</param>
        public void RemoveTag(string artist, string album, string tag)
        {
            // TODO: Test AlbumApi.RemoveTag(string artist, string album, string tag)
            if (!this.lastfmApi.Session.Authenticated)
            {
                throw new LastfmNetException("This service requires authentication.");
            }

            LastfmParameters parameters = new LastfmParameters("album.removeTag");
            parameters["artist"] = artist;
            parameters["album"] = album;
            parameters["tag"] = tag;
            parameters.AddApiKey(this.lastfmApi.Session.ApiKey);
            parameters.AddSessionKey(this.lastfmApi.Session.SessionKey);
            parameters.Sign(this.lastfmApi.Session.ApiSecret);

            try
            {
                this.lastfmApi.MakeRequest(parameters, HttpMethod.Post);
            }
            catch (Exception ex)
            {
                throw new LastfmNetException("Unable to remove tag from artist! (" + ex.Message + ")", ex);
            }
        }

        /// <summary>
        /// Search for an album by name. Returns album matches sorted by relevance. 
        /// This service does not require authentication. 
        /// </summary>
        /// <param name="album">The album name</param>
        /// <param name="page">The page number to fetch. Defaults to first page.</param>
        /// <param name="limit">The number of results to fetch per page. Defaults to 30.</param>
        /// <returns>Album matches sorted by relevance.</returns>
        public LastfmOpenSearch<Album> Search(string album, int page = 1, int limit = 50)
        {
            LastfmParameters parameters = new LastfmParameters("album.search");
            parameters["page"] = page.ToString(CultureInfo.InvariantCulture);
            parameters["limit"] = limit.ToString(CultureInfo.InvariantCulture);
            parameters["album"] = album;
            parameters.AddApiKey(this.lastfmApi.Session.ApiKey);

            try
            {
                LastfmResponse result = this.lastfmApi.MakeRequest(parameters);
                return new LastfmOpenSearch<Album>(result.MainNode, "albummatches");
            }
            catch (Exception ex)
            {
                throw new LastfmNetException("Unable to search for the album! (" + ex.Message + ")", ex);
            }
        }

        /// <summary>
        /// Share an album with one or more Last.fm users or other friends. 
        /// This service requires authentication. 
        /// </summary>
        /// <param name="artist">An artist name.</param>
        /// <param name="album">An album name.</param>
        /// <param name="recipients">Email Address | Last.fm Username - A comma delimited list of email addresses or Last.fm usernames. Maximum is 10.</param>
        /// <param name="public">Optionally show in the sharing users activity feed. Defaults to 0 (false).</param>
        /// <param name="message">An optional message to send with the recommendation. If not supplied a default message will be used.</param>
        /// <remarks>Not supporting emails!</remarks>
        public void Share(string artist, string album, IList<string> recipients, bool @public = false, string message = null)
        {
            // TODO: Not supporting emails? Why?
            if (!this.lastfmApi.Session.Authenticated)
            {
                throw new LastfmNetException("This service requires authentication.");
            }

            if (recipients == null)
            {
                throw new ArgumentNullException("recipients", "You must give a list of recipients.");
            }

            if (recipients.Count == 0 || recipients.Count > 10)
            {
                throw new ArgumentException("You must give at least one recipient and at most 10.");
            }

            LastfmParameters parameters = new LastfmParameters("album.share");
            parameters["artist"] = artist;
            parameters["album"] = album;
            parameters["recipient"] = string.Join(",", recipients);
            parameters["public"] = @public.ToInteger().ToString(CultureInfo.InvariantCulture);
            if (message != null)
            {
                parameters["message"] = message;
            }

            parameters.AddApiKey(this.lastfmApi.Session.ApiKey);
            parameters.AddSessionKey(this.lastfmApi.Session.SessionKey);
            parameters.Sign(this.lastfmApi.Session.ApiSecret);
            try
            {
                this.lastfmApi.MakeRequest(parameters, HttpMethod.Post);
            }
            catch (Exception ex)
            {
                throw new LastfmNetException("Unable to share album! (" + ex.Message + ")", ex);
            }
        }
    }
}
